package me.spring.cloud.common.components.properties;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.core.env.Environment;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * @author luffy
 * @description: 替换占位符
 * @date 2019-05-15 11:25
 */
public class CustomerPropertyPlaceholderHelper {

  private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4);

  static {
    wellKnownSimplePrefixes.put("}", "{");
    wellKnownSimplePrefixes.put("]", "[");
    wellKnownSimplePrefixes.put(")", "(");
  }

  private final String placeholderPrefix;

  private final String placeholderSuffix;

  private final String simplePrefix;

  @Nullable
  private final String valueSeparator;

  private final boolean ignoreUnresolvablePlaceholders;

  /**
   * Creates a new {@code CustomerPropertyPlaceholderHelper} that uses the supplied prefix and suffix. Unresolvable
   * placeholders are ignored.
   *
   * @param placeholderPrefix the prefix that denotes the start of a placeholder
   * @param placeholderSuffix the suffix that denotes the end of a placeholder
   */
  public CustomerPropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
    this(placeholderPrefix, placeholderSuffix, null, true);
  }

  /**
   * Creates a new {@code CustomerPropertyPlaceholderHelper} that uses the supplied prefix and suffix.
   *
   * @param placeholderPrefix the prefix that denotes the start of a placeholder
   * @param placeholderSuffix the suffix that denotes the end of a placeholder
   * @param valueSeparator the separating character between the placeholder variable and the associated default value,
   * if any
   * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored ({@code true})
   * or cause an exception ({@code false})
   */
  public CustomerPropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
      String valueSeparator, boolean ignoreUnresolvablePlaceholders) {

    Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
    Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
    this.placeholderPrefix = placeholderPrefix;
    this.placeholderSuffix = placeholderSuffix;
    String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
    if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
      this.simplePrefix = simplePrefixForSuffix;
    } else {
      this.simplePrefix = this.placeholderPrefix;
    }
    this.valueSeparator = valueSeparator;
    this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
  }

  public String resolvePlaceholder(Environment environment, String key) {
    return this.parseStringValue(environment, key, new HashSet<>());
  }

  /**
   * @Return java.lang.String
   * @Author luffy
   * @Description 递归解析带占位符的属性为字符串
   * @Date 2019-05-15 11:34
   * @Param [value, visitedPlaceholders]
   **/
  protected String parseStringValue(Environment environment, String value, Set<String> visitedPlaceholders) {
    StringBuilder result = new StringBuilder(value);
    int startIndex = value.indexOf(this.placeholderPrefix);
    while (startIndex != -1) {
      //搜索第一个占位符后缀的索引
      int endIndex = findPlaceholderEndIndex(result, startIndex);
      if (endIndex != -1) {
        //提取第一个占位符中的原始字符串，如${server.port}->server.port
        String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
        String originalPlaceholder = placeholder;
        //判重
        if (!visitedPlaceholders.add(originalPlaceholder)) {
          throw new IllegalArgumentException(
              "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
        }
        // Recursive invocation, parsing placeholders contained in the placeholder key.
        // 递归调用，实际上就是解析嵌套的占位符，因为提取的原始字符串有可能还有一层或者多层占位符
        placeholder = parseStringValue(environment, placeholder, visitedPlaceholders);
        // Now obtain the value for the fully resolved key...
        // 递归调用完毕后，可以确定得到的字符串一定是不带占位符，这个时候调用getPropertyAsRawString获取key对应的字符串值
        String propVal = environment.getProperty(placeholder);
        // 如果字符串值为null，则进行默认值的解析，因为默认值有可能也使用了占位符，如${server.port:${server.port-2:8080}}
        if (propVal == null && this.valueSeparator != null) {
          int separatorIndex = placeholder.indexOf(this.valueSeparator);
          if (separatorIndex != -1) {
            String actualPlaceholder = placeholder.substring(0, separatorIndex);
            // 提取默认值的字符串
            String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
            // 这里是把默认值的表达式做一次解析，解析到null，则直接赋值为defaultValue
            propVal = environment.getProperty(placeholder);
            if (propVal == null) {
              propVal = defaultValue;
            }
          }
        }
        // 上一步解析出来的值不为null，但是它有可能是一个带占位符的值，所以后面对值进行递归解析
        if (propVal != null) {
          // Recursive invocation, parsing placeholders contained in the
          // previously resolved placeholder value.
          propVal = parseStringValue(environment, propVal, visitedPlaceholders);
          // 这一步很重要，替换掉第一个被解析完毕的占位符属性，例如${server.port}-${spring.application.name} -> 9090--${spring.application.name}
          result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
          // 重置startIndex为下一个需要解析的占位符前缀的索引，可能为-1，说明解析结束
          startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
        } else if (this.ignoreUnresolvablePlaceholders) {
          // 如果propVal为null并且ignoreUnresolvablePlaceholders设置为true，直接返回当前的占位符之间的原始字符串尾的索引，也就是跳过解析
          // Proceed with unprocessed value.
          startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
        } else {
          // 如果propVal为null并且ignoreUnresolvablePlaceholders设置为false，抛出异常
          throw new IllegalArgumentException(
              "Could not resolve placeholder '" + placeholder + "'" + " in value \"" + value + "\"");
        }
        // 递归结束移除判重集合中的元素
        visitedPlaceholders.remove(originalPlaceholder);
      } else {
        // endIndex = -1说明解析结束
        startIndex = -1;
      }
    }
    return result.toString();
  }

  //基于传入的起始索引，搜索第一个占位符后缀的索引，兼容嵌套的占位符
  private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
    //这里index实际上就是实际需要解析的属性的第一个字符，如${server.port}，这里index指向s
    int index = startIndex + this.placeholderPrefix.length();
    int withinNestedPlaceholder = 0;
    while (index < buf.length()) {
      //index指向"}"，说明有可能到达占位符尾部或者嵌套占位符尾部
      if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
        //存在嵌套占位符，则返回字符串中占位符后缀的索引值
        if (withinNestedPlaceholder > 0) {
          withinNestedPlaceholder--;
          index = index + this.placeholderSuffix.length();
        } else {
          //不存在嵌套占位符，直接返回占位符尾部索引
          return index;
        }
      }
      //index指向"{"，记录嵌套占位符个数withinNestedPlaceholder加1，index更新为嵌套属性的第一个字符的索引
      else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
        withinNestedPlaceholder++;
        index = index + this.simplePrefix.length();
      } else {
        //index不是"{"或者"}"，则进行自增
        index++;
      }
    }
    //这里说明解析索引已经超出了原字符串
    return -1;
  }
}
